; 编译链接方法

;nasm -f elf -o kernel.o kernel.asm
;gcc -c -o main.o main.c
;ld -s -Ttext 0x30400  -o kernel.bin kernel.o main.o font.o lib.o


;常量定义
selectorKernelCs  equ 8

P_STACKBASE	equ	0
GSREG		equ	P_STACKBASE
FSREG		equ	GSREG		+ 4
ESREG		equ	FSREG		+ 4
DSREG		equ	ESREG		+ 4
EDIREG		equ	DSREG		+ 4
ESIREG		equ	EDIREG		+ 4
EBPREG		equ	ESIREG		+ 4
KERNELESPREG	equ	EBPREG		+ 4
EBXREG		equ	KERNELESPREG	+ 4
EDXREG		equ	EBXREG		+ 4
ECXREG		equ	EDXREG		+ 4
EAXREG		equ	ECXREG		+ 4
RETADR		equ	EAXREG		+ 4
EIPREG		equ	RETADR		+ 4
CSREG		equ	EIPREG		+ 4
EFLAGSREG	equ	CSREG		+ 4
ESPREG		equ	EFLAGSREG	+ 4
SSREG		equ	ESPREG		+ 4
P_STACKTOP	equ	SSREG		+ 4
P_LDT_SEL	equ	P_STACKTOP
P_LDT		equ	P_LDT_SEL	+ 4

TSS3_S_SP0	equ	4
;中断相关
INT_M_CTL       equ     0x20
INT_M_CTLMASK   equ     0x21
INT_S_CTL       equ     0xa0
INT_S_CTLMASK   equ     0xa1
EOI             equ     0x20

; 以下选择子值必须与 protect.h 中保持一致!!!
SELECTOR_FLAT_C		equ		0x08	; LOADER 里面已经确定了的.
SELECTOR_TSS		equ		0x20	; TSS
SELECTOR_KERNEL_CS	equ		SELECTOR_FLAT_C
;系统调用相关
NR_GET_TICKS            equ             0
NR_SENDREC	        equ             1
INT_VECTOR_SYS_CALL     equ             0x90


;导入外部变量
extern  main              ;引入c编写的主函数  main.c 中主函数
extern  initGdtAndIdt     ;初始化新的gdt 和idt

extern  gdtPtr            ;保存gdt 基地址和偏移地址 在main.c 中定义
extern  idtPtr            ;保存idt 基地址和偏移地址 在main.c 中定义
extern	exception_handler ;中断和异常处理程序

extern	p_proc_ready
extern	tss
extern  k_reenter
extern  irq_table
extern  sys_call_table ;系统调用table


[bits 32]
; 内核堆栈段定义
[section .bss]
stackSpace  resb   2*1024    ;2k
stackTop:                    ;栈顶

; 内核代码段
[section .text]	             ; 代码段
global _start	             ; 导出 _start
global init                  ;
global io_hlt                ; 导出 _io_hlt  这里前边不用加下滑线
global io_cli                ;
global io_sti                ;
global io_stihlt             ;
global io_in8                ;
global io_in16               ;
global io_in32               ;
global io_out8               ;
global io_out16              ;
global io_out32              ;
global io_load_eflags        ;
global io_store_eflags       ;

;中断和异常处理程序
global	divide_error
global	single_step_exception
global	nmi
global	breakpoint_exception
global	overflow
global	bounds_check
global	inval_opcode
global	copr_not_available
global	double_fault
global	copr_seg_overrun
global	inval_tss
global	segment_not_present
global	stack_exception
global	general_protection
global	page_fault
global	copr_error
global  csInit
global  hwint00
global  enable_irq
global  disable_irq
global  get_ticks ;获取时钟滴答
global  sys_call  ;系统调用







; 跳到这里来的时候
_start:       
        mov esp ,stackTop ;从loader 中把堆栈切换到kernel 中
	sgdt [gdtPtr]  ;保存gdt 到全局变量 gdtPtr

	call initGdtAndIdt   ;初始化gdt
	lgdt [gdtPtr]  ;重新加载新的gdt
	lidt [idtPtr]  ;加载idt
	jmp selectorKernelCs:init ;切换到新的代码段上

init:
        call main      ;调用c 中主函数

;人为产生测试异常
csInit:
         ;ud2
	 xor eax,eax
	 mov ax,SELECTOR_TSS
	 ltr ax
	 jmp restart


;从ring0 切换到ring3
;restart:
;	mov	esp, [p_proc_ready]
;	lldt	[esp + P_LDT_SEL] 
;	lea	eax, [esp + P_STACKTOP]
;	mov	dword [tss + TSS3_S_SP0], eax
;
;	pop	gs
;	pop	fs
;	pop	es
;	pop	ds
;	popad
;
;	add	esp, 4
;
;	iretd

restart: ;非中断重入时进行进程ldt切换
       mov esp,[p_proc_ready] ;离开内核栈,准备从进程表堆栈中返回 ring3
       lldt [esp+P_LDT_SEL];切换ldt
       lea eax ,[esp+P_STACKTOP]
       mov dword [tss+TSS3_S_SP0],eax ; 把tss sp0 指向进程表堆栈最高处 为下次中断压栈做准备

restart_reenter:  ;如果k_reenter!=0会跳到这里执行
       dec dword [k_reenter]
       pop gs
       pop fs
       pop es
       pop ds
       popad

       add esp,4 ;跳过 retaddr

       iretd



;中断处理程序
;typedef struct s_stackframe ;进程堆栈
;{
;	u32 gs;
;	u32 fs;
;	u32 es;
;	u32 ds;
;	u32 edi;
;	u32 esi;
;	u32 ebp;
;	u32 kernel_esp;
;	u32 ebx;
;	u32 edx;
;	u32 ecx;
;	u32 eax;
;	u32 retaddr;
;       u32 eip;
;	u32 cs;
;	u32 eflags;
;	u32 esp;
;	u32 ss;
;
;} STACK_FRAME;

%macro  hwint_matster 1  ; 1代表参数有一个,该参数为中断号
       call save    ;  call save 会把当前eip,也就是下一条指令的地址 压入继 ss,esp,eflags,cs,eip 之后的 retaddr            
      
       in al,INT_M_CTLMASK  ;屏蔽时钟中断
       or al,(1<<%1)
       out INT_M_CTLMASK,al

       mov al,EOI       ;发送请求 中断已处理
       out INT_M_CTL,al

  
       sti ;开中断 ,在响应中断过程中会自动关闭中断,需要人为打开
	       ;
	       ;这中间是中断业务处理逻辑
	       ;
	       push %1 ;中断号入栈
	       call [irq_table+4* %1]
	       pop ecx 
       cli ;关中断

       in  al,INT_M_CTLMASK  ;打开时钟中断
       and al, ~(1<<%1)
       out INT_M_CTLMASK,al  

       ret  ; 跳入 push .restart_v2 或 push .restart_reenter_v2 执行
%endmacro




save :
       pushad
       push ds
       push es
       push fs
       push gs

       mov esi,edx ;保存edx ,edx 保存系统调用参数

       ;设置ds ,es 和ss相同
       mov dx,ss
       mov ds,dx
       mov es,dx

       mov edx ,esi ;恢复edx
       mov esi ,esp;  此时esp 指向gs ,即堆栈的最上边

       inc dword [k_reenter]
       cmp dword [k_reenter],0
       jne .1                         ;中断重入时跳到 .1
       mov esp,stackTop               ; 切换到内核栈,从现在开始可以调用函数使用内核栈了
       push restart                   ;压入非中断重入 ret 的返回地址
       jmp [esi+RETADR -P_STACKBASE]  ;跳转到 retaddr 保存的代码处, RETADR -P_STACKBASE 是retaddr 在堆栈中便宜 esp+偏移 就是retaddr内存地址
 .1:    
       push restart_reenter           ;压入中断重入 ret 的返回地址
       jmp [esi+RETADR -P_STACKBASE]

align 16
hwint00:
       hwint_matster 0  

       ;中断发生时 ss,esp,eflags,cs,eip 相继入栈  ,ss 已经从tss 取出
       ;sub esp,4 ;为 retaddr 留出位置 ,暂时没有用处
      
       ;call save    ;  call save 会把当前eip,也就是下一条指令的地址 压入继 ss,esp,eflags,cs,eip 之后的 retaddr            
      
       ;in al,INT_M_CTLMASK  ;屏蔽时钟中断
       ;or al,1
       ;out INT_M_CTLMASK,al

       ;mov al,EOI       ;发送请求 中断已处理
       ;out INT_M_CTL,al

  
       ;sti ;开中断 ,在响应中断过程中会自动关闭中断,需要人为打开
	       ;
	       ;这中间是中断业务处理逻辑
	       ;
	;       push 0
	;       call clockHandler
	;       add esp,4
       ;cli ;关中断

       ;in  al,INT_M_CTLMASK  ;打开时钟中断
       ;mov al,0xfe
       ;out INT_M_CTLMASK,al  
       ;ret  ; 跳入 push .restart_v2 或 push .restart_reenter_v2 执行



;.restart_v2: ;非中断重入时进行进程ldt切换
;       mov esp,[p_proc_ready] ;离开内核栈,准备从进程表堆栈中返回 ring3
;       lldt [esp+P_LDT_SEL];切换ldt
;       lea eax ,[esp+P_STACKTOP]
;       mov dword [tss+TSS3_S_SP0],eax ; 把tss sp0 指向进程表堆栈最高处 为下次中断压栈做准备
;
;.restart_reenter_v2:  ;如果k_reenter!=0会跳到这里执行
;       dec dword [k_reenter]
;       pop gs
;       pop fs
;       pop es
;       pop ds
;       popad
;
;       add esp,4 ;跳过 retaddr
;
;       iretd




; 中断和异常 -- 异常
divide_error:
	push	0xFFFFFFFF	; no err code
	push	0		; vector_no	= 0
	jmp	exception
single_step_exception:
	push	0xFFFFFFFF	; no err code
	push	1		; vector_no	= 1
	jmp	exception
nmi:
	push	0xFFFFFFFF	; no err code
	push	2		; vector_no	= 2
	jmp	exception
breakpoint_exception:
	push	0xFFFFFFFF	; no err code
	push	3		; vector_no	= 3
	jmp	exception
overflow:
	push	0xFFFFFFFF	; no err code
	push	4		; vector_no	= 4
	jmp	exception
bounds_check:
	push	0xFFFFFFFF	; no err code
	push	5		; vector_no	= 5
	jmp	exception
inval_opcode:
	push	0xFFFFFFFF	; no err code
	push	6		; vector_no	= 6
	jmp	exception
copr_not_available:
	push	0xFFFFFFFF	; no err code
	push	7		; vector_no	= 7
	jmp	exception
double_fault:
	push	8		; vector_no	= 8
	jmp	exception
copr_seg_overrun:
	push	0xFFFFFFFF	; no err code
	push	9		; vector_no	= 9
	jmp	exception
inval_tss:
	push	10		; vector_no	= A
	jmp	exception
segment_not_present:
	push	11		; vector_no	= B
	jmp	exception
stack_exception:
	push	12		; vector_no	= C
	jmp	exception
general_protection:
	push	13		; vector_no	= D
	jmp	exception
page_fault:
	push	14		; vector_no	= E
	jmp	exception
copr_error:
	push	0xFFFFFFFF	; no err code
	push	16		; vector_no	= 10h
	jmp	exception

exception:
	call	exception_handler
	add	esp, 4*2	; 让栈顶指向 EIP，堆栈中从顶向下依次是：EIP、CS、EFLAGS
	hlt



; void io_hlt() 停机函数,c 中没有功能实现hlt 指令
io_hlt: 
        hlt
        ret

;void io_cli()
io_cli:
        cli
	ret

;void io_sti()
io_sti:
        sti
	ret

;void io_stihlt()
io_stihlt:
        sti
	hlt
	ret

;int io_in8(int port)
io_in8:
       mov edx ,[esp+4] ;取port
       mov eax ,0       ;返回值清零
       in  al  ,dx
       ret

;int io_in16(int port)
io_in16:
       mov edx ,[esp+4] ;取port
       mov eax ,0       ;返回值清零
       in  ax  ,dx
       ret

;int io_in32(int port)
io_in32:
       mov edx ,[esp+4] ;取port
       mov eax ,0       ;返回值清零
       in  eax  ,dx
       ret

; void io_out8(int port,int data)
io_out8:
       mov edx,[esp+4]
       mov al ,[esp+8]
       out dx,al
       ret

; void io_out16(int port,int data)
io_out16:
       mov edx ,[esp+4]
       mov eax ,[esp+8]
       out dx,ax
       ret

; void io_out32(int port,int data)
io_out32:
       mov edx ,[esp+4]
       mov eax ,[esp+8]
       out dx,eax
       ret
;int io_load_eflags()
io_load_eflags:
       pushfd
       pop eax
       ret
;void io_store_eflags(int eflags)
io_store_eflags:
       mov  eax,[esp+4]
       push eax
       popfd
       ret


; ========================================================================
;                  void disable_irq(int irq);
; ========================================================================
; Disable an interrupt request line by setting an 8259 bit.
; Equivalent code:
;	if(irq < 8)
;		out_byte(INT_M_CTLMASK, in_byte(INT_M_CTLMASK) | (1 << irq));
;	else
;		out_byte(INT_S_CTLMASK, in_byte(INT_S_CTLMASK) | (1 << irq));
disable_irq:
        mov     ecx, [esp + 4]          ; irq
        pushf
        cli
        mov     ah, 1
        rol     ah, cl                  ; ah = (1 << (irq % 8))
        cmp     cl, 8
        jae     disable_8               ; disable irq >= 8 at the slave 8259
disable_0:
        in      al, INT_M_CTLMASK
        test    al, ah
        jnz     dis_already             ; already disabled?
        or      al, ah
        out     INT_M_CTLMASK, al       ; set bit at master 8259
        popf
        mov     eax, 1                  ; disabled by this function
        ret
disable_8:
        in      al, INT_S_CTLMASK
        test    al, ah
        jnz     dis_already             ; already disabled?
        or      al, ah
        out     INT_S_CTLMASK, al       ; set bit at slave 8259
        popf
        mov     eax, 1                  ; disabled by this function
        ret
dis_already:
        popf
        xor     eax, eax                ; already disabled
        ret

; ========================================================================
;                  void enable_irq(int irq);
; ========================================================================
; Enable an interrupt request line by clearing an 8259 bit.
; Equivalent code:
;       if(irq < 8)
;               out_byte(INT_M_CTLMASK, in_byte(INT_M_CTLMASK) & ~(1 << irq));
;       else
;               out_byte(INT_S_CTLMASK, in_byte(INT_S_CTLMASK) & ~(1 << irq));
;
enable_irq:
        mov     ecx, [esp + 4]          ; irq
        pushf
        cli
        mov     ah, ~1
        rol     ah, cl                  ; ah = ~(1 << (irq % 8))
        cmp     cl, 8
        jae     enable_8                ; enable irq >= 8 at the slave 8259
enable_0:
        in      al, INT_M_CTLMASK
        and     al, ah
        out     INT_M_CTLMASK, al       ; clear bit at master 8259
        popf
        ret
enable_8:
        in      al, INT_S_CTLMASK
        and     al, ah
        out     INT_S_CTLMASK, al       ; clear bit at slave 8259
        popf
        ret

;系统调用入口,获取时钟滴答
get_ticks:
        mov eax,NR_GET_TICKS
	int INT_VECTOR_SYS_CALL
	ret
;系统调用入口,发送或接收信息
sendrec:
	mov	eax, NR_SEND_REC ;系统调用号
	mov	ebx, [esp + 4]	 ; function 功能:SEND OR RECEIVE
	mov	ecx, [esp + 8]	 ; src_dest
	mov	edx, [esp + 12]	 ; p_msg
	int	INT_VECTOR_SYS_CALL
	ret

; int sys_sendrec(int function ,int src_dest,MESSAGE* msg,struct proc *p)

sys_call :
         call save
	 sti
	 push esi
        
	 push dword[p_proc_ready] ;当前进程
         push edx                 ;msg
	 push ecx                 ;src_dest
	 push ebx                 ;function
	 call [sys_call_table+eax*4]
	 add  esp,4*4             ;跳过四个参数

         pop esi
	 mov  [esi+EAXREG-P_STACKBASE],eax  ;把返回值保存到进程eax 寄存器位置中
	 cli
	 ret